<!-- Licensed under a BSD license. See license.html for license -->
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf8">
  <title>WebGL Text - Glyphs</title>
  <link type="text/css" href="../../resources/webgl-tutorials.css" rel="stylesheet" />
  <!--
for most samples webgl-utils only provides shader compiling/linking and
canvas resizing because why clutter the examples with code that's the same in every sample.
See http://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html
and http://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
for webgl-utils, m3, m4, and webgl-lessons-ui.
-->
  <script src="../../resources/webgl-utils.js"></script>
  <script src="../../resources/m4.js"></script>
  <script src="../../resources/primitives.js"></script>
</head>

<body>
  <div id="info">
    <a href="https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-text-glyphs.html" target="_blank">WebGL -
      文字 - 使用字形纹理</a>
  </div>
  <canvas id="canvas"></canvas>
</body>
<!-- vertex shader -->
<script id="3d-vertex-shader" type="x-shader/x-vertex">
attribute vec4 a_position;
attribute vec4 a_color;

uniform mat4 u_matrix;

varying vec4 v_color;

void main() {
  // Multiply the position by the matrix.
  gl_Position = u_matrix * a_position;

  // Pass the color to the fragment shader.
  v_color = a_color;
}
</script>
<!-- fragment shader -->
<script id="3d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

// Passed in from the vertex shader.
varying vec4 v_color;

void main() {
   gl_FragColor = v_color;
}
</script>
<script id="text-vertex-shader" type="x-shader/x-vertex">
attribute vec4 a_position;
attribute vec2 a_texcoord;

uniform mat4 u_matrix;

varying vec2 v_texcoord;

void main() {
  // Multiply the position by the matrix.
  gl_Position = u_matrix * a_position;

  // Pass the texcoord to the fragment shader.
  v_texcoord = a_texcoord;
}
</script>
<!-- fragment shader -->
<script id="text-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

// Passed in from the vertex shader.
varying vec2 v_texcoord;

uniform sampler2D u_texture;

void main() {
   gl_FragColor = texture2D(u_texture, v_texcoord);
}
</script>
<script>
  "use strict";

  var fontInfo = {
    letterHeight: 8,
    spaceWidth: 8,
    spacing: -1,
    textureWidth: 64,
    textureHeight: 40,
    glyphInfos: {
      'a': { x: 0, y: 0, width: 8, },
      'b': { x: 8, y: 0, width: 8, },
      'c': { x: 16, y: 0, width: 8, },
      'd': { x: 24, y: 0, width: 8, },
      'e': { x: 32, y: 0, width: 8, },
      'f': { x: 40, y: 0, width: 8, },
      'g': { x: 48, y: 0, width: 8, },
      'h': { x: 56, y: 0, width: 8, },
      'i': { x: 0, y: 8, width: 8, },
      'j': { x: 8, y: 8, width: 8, },
      'k': { x: 16, y: 8, width: 8, },
      'l': { x: 24, y: 8, width: 8, },
      'm': { x: 32, y: 8, width: 8, },
      'n': { x: 40, y: 8, width: 8, },
      'o': { x: 48, y: 8, width: 8, },
      'p': { x: 56, y: 8, width: 8, },
      'q': { x: 0, y: 16, width: 8, },
      'r': { x: 8, y: 16, width: 8, },
      's': { x: 16, y: 16, width: 8, },
      't': { x: 24, y: 16, width: 8, },
      'u': { x: 32, y: 16, width: 8, },
      'v': { x: 40, y: 16, width: 8, },
      'w': { x: 48, y: 16, width: 8, },
      'x': { x: 56, y: 16, width: 8, },
      'y': { x: 0, y: 24, width: 8, },
      'z': { x: 8, y: 24, width: 8, },
      '0': { x: 16, y: 24, width: 8, },
      '1': { x: 24, y: 24, width: 8, },
      '2': { x: 32, y: 24, width: 8, },
      '3': { x: 40, y: 24, width: 8, },
      '4': { x: 48, y: 24, width: 8, },
      '5': { x: 56, y: 24, width: 8, },
      '6': { x: 0, y: 32, width: 8, },
      '7': { x: 8, y: 32, width: 8, },
      '8': { x: 16, y: 32, width: 8, },
      '9': { x: 24, y: 32, width: 8, },
      '-': { x: 32, y: 32, width: 8, },
      '*': { x: 40, y: 32, width: 8, },
      '!': { x: 48, y: 32, width: 8, },
      '?': { x: 56, y: 32, width: 8, },
    },
  };


  function makeVerticesForString(fontInfo, s) {
    var len = s.length;
    var numVertices = len * 6;
    var positions = new Float32Array(numVertices * 2);
    var texcoords = new Float32Array(numVertices * 2);
    var offset = 0;
    var x = 0;
    var maxX = fontInfo.textureWidth;
    var maxY = fontInfo.textureHeight;
    for (var ii = 0; ii < len; ++ii) {
      var letter = s[ii];
      var glyphInfo = fontInfo.glyphInfos[letter];
      if (glyphInfo) {
        var x2 = x + glyphInfo.width;
        var u1 = glyphInfo.x / maxX;
        var v1 = (glyphInfo.y + fontInfo.letterHeight - 1) / maxY;
        var u2 = (glyphInfo.x + glyphInfo.width - 1) / maxX;
        var v2 = glyphInfo.y / maxY;

        // 6 vertices per letter
        positions[offset + 0] = x;
        positions[offset + 1] = 0;
        texcoords[offset + 0] = u1;
        texcoords[offset + 1] = v1;

        positions[offset + 2] = x2;
        positions[offset + 3] = 0;
        texcoords[offset + 2] = u2;
        texcoords[offset + 3] = v1;

        positions[offset + 4] = x;
        positions[offset + 5] = fontInfo.letterHeight;
        texcoords[offset + 4] = u1;
        texcoords[offset + 5] = v2;

        positions[offset + 6] = x;
        positions[offset + 7] = fontInfo.letterHeight;
        texcoords[offset + 6] = u1;
        texcoords[offset + 7] = v2;

        positions[offset + 8] = x2;
        positions[offset + 9] = 0;
        texcoords[offset + 8] = u2;
        texcoords[offset + 9] = v1;

        positions[offset + 10] = x2;
        positions[offset + 11] = fontInfo.letterHeight;
        texcoords[offset + 10] = u2;
        texcoords[offset + 11] = v2;

        x += glyphInfo.width + fontInfo.spacing;
        offset += 12;
      } else {
        // we don't have this character so just advance
        x += fontInfo.spaceWidth;
      }
    }

    // return ArrayBufferViews for the portion of the TypedArrays
    // that were actually used.
    return {
      arrays: {
        position: new Float32Array(positions.buffer, 0, offset),
        texcoord: new Float32Array(texcoords.buffer, 0, offset),
      },
      numVertices: offset / 2,
    };
  }

  function main() {
    // Get A WebGL context
    /** @type {HTMLCanvasElement} */
    var canvas = document.getElementById("canvas");
    var gl = canvas.getContext("webgl");
    if (!gl) {
      return;
    }

    // Create data for 'F'
    var fBufferInfo = primitives.create3DFBufferInfo(gl);
    // Maunally create a bufferInfo
    var textBufferInfo = {
      attribs: {
        a_position: { buffer: gl.createBuffer(), numComponents: 2, },
        a_texcoord: { buffer: gl.createBuffer(), numComponents: 2, },
      },
      numElements: 0,
    };

    // setup GLSL programs
    var fProgramInfo = webglUtils.createProgramInfo(gl, ["3d-vertex-shader", "3d-fragment-shader"]);
    var textProgramInfo = webglUtils.createProgramInfo(gl, ["text-vertex-shader", "text-fragment-shader"]);

    // Create a texture.
    var glyphTex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, glyphTex);
    // Fill the texture with a 1x1 blue pixel.
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
      new Uint8Array([0, 0, 255, 255]));
    // Asynchronously load an image
    var image = new Image();
    image.src = "../../resources/8x8-font.png";
    image.addEventListener('load', function () {
      // Now that the image has loaded make copy it to the texture.
      gl.bindTexture(gl.TEXTURE_2D, glyphTex);
      gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    });

    var names = [
      "anna",   // 0
      "colin",  // 1
      "james",  // 2
      "danny",  // 3
      "kalin",  // 4
      "hiro",   // 5
      "eddie",  // 6
      "shu",    // 7
      "brian",  // 8
      "tami",   // 9
      "rick",   // 10
      "gene",   // 11
      "natalie",// 12,
      "evan",   // 13,
      "sakura", // 14,
      "kai",    // 15,
    ];

    var fUniforms = {
      u_matrix: m4.identity(),
    };

    var textUniforms = {
      u_matrix: m4.identity(),
      u_texture: glyphTex,
      u_color: [0, 0, 0, 1],  // black
    };

    function radToDeg(r) {
      return r * 180 / Math.PI;
    }

    function degToRad(d) {
      return d * Math.PI / 180;
    }

    var translation = [0, 30, 0];
    var rotation = [degToRad(190), degToRad(0), degToRad(0)];
    var scale = [1, 1, 1];
    var fieldOfViewRadians = degToRad(60);
    var rotationSpeed = 1.2;

    var then = 0;

    requestAnimationFrame(drawScene);

    // Draw the scene.
    function drawScene(now) {
      // Convert to seconds
      now *= 0.001;
      // Subtract the previous time from the current time
      var deltaTime = now - then;
      // Remember the current time for the next frame.
      then = now;

      webglUtils.resizeCanvasToDisplaySize(gl.canvas);

      // Tell WebGL how to convert from clip space to pixels
      gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

      // Every frame increase the rotation a little.
      rotation[1] += rotationSpeed * deltaTime;

      gl.enable(gl.CULL_FACE);
      gl.enable(gl.DEPTH_TEST);
      gl.disable(gl.BLEND);
      gl.depthMask(true);

      // Clear the canvas AND the depth buffer.
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

      // Compute the matrices used for all objects
      var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
      var zNear = 1;
      var zFar = 2000;
      var projectionMatrix = m4.perspective(fieldOfViewRadians, aspect, zNear, zFar);

      // Compute the camera's matrix using look at.
      var cameraRadius = 360;
      var cameraPosition = [Math.cos(now) * cameraRadius, 0, Math.sin(now) * cameraRadius];
      var target = [0, 0, 0];
      var up = [0, 1, 0];
      var cameraMatrix = m4.lookAt(cameraPosition, target, up);
      var viewMatrix = m4.inverse(cameraMatrix);

      var textPositions = [];

      // setup to draw the 'F'
      gl.useProgram(fProgramInfo.program);

      webglUtils.setBuffersAndAttributes(gl, fProgramInfo, fBufferInfo);

      // draw the Fs.
      var spread = 170;
      for (var yy = -1; yy <= 1; ++yy) {
        for (var xx = -2; xx <= 2; ++xx) {
          var fViewMatrix = m4.translate(viewMatrix,
            translation[0] + xx * spread, translation[1] + yy * spread, translation[2]);
          fViewMatrix = m4.xRotate(fViewMatrix, rotation[0]);
          fViewMatrix = m4.yRotate(fViewMatrix, rotation[1] + yy * xx * 0.2);
          fViewMatrix = m4.zRotate(fViewMatrix, rotation[2] + now + (yy * 3 + xx) * 0.1);
          fViewMatrix = m4.scale(fViewMatrix, scale[0], scale[1], scale[2]);
          fViewMatrix = m4.translate(fViewMatrix, -50, -75, 0);
          textPositions.push([fViewMatrix[12], fViewMatrix[13], fViewMatrix[14]]);

          fUniforms.u_matrix = m4.multiply(projectionMatrix, fViewMatrix);

          webglUtils.setUniforms(fProgramInfo, fUniforms);

          // Draw the geometry.
          gl.drawElements(gl.TRIANGLES, fBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
        }
      }

      gl.enable(gl.BLEND);
      gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
      gl.depthMask(false);

      // draw the text

      // setup to draw the text.
      // Because every letter uses the same attributes and the same progarm
      // we only need to do this once.
      gl.useProgram(textProgramInfo.program);
      webglUtils.setBuffersAndAttributes(gl, textProgramInfo, textBufferInfo);

      textPositions.forEach(function (pos, ndx) {

        var name = names[ndx];
        var s = name + ":" + pos[0].toFixed(0) + "," + pos[1].toFixed(0) + "," + pos[2].toFixed(0);
        var vertices = makeVerticesForString(fontInfo, s);

        // update the buffers
        textBufferInfo.attribs.a_position.numComponents = 2;
        gl.bindBuffer(gl.ARRAY_BUFFER, textBufferInfo.attribs.a_position.buffer);
        gl.bufferData(gl.ARRAY_BUFFER, vertices.arrays.position, gl.DYNAMIC_DRAW);
        gl.bindBuffer(gl.ARRAY_BUFFER, textBufferInfo.attribs.a_texcoord.buffer);
        gl.bufferData(gl.ARRAY_BUFFER, vertices.arrays.texcoord, gl.DYNAMIC_DRAW);

        // use just the position of the 'F' for the text

        // because pos is in view space that means it's a vector from the eye to
        // some position. So translate along that vector back toward the eye some distance
        var fromEye = m4.normalize(pos);
        var amountToMoveTowardEye = 150;  // because the F is 150 units long
        var viewX = pos[0] - fromEye[0] * amountToMoveTowardEye;
        var viewY = pos[1] - fromEye[1] * amountToMoveTowardEye;
        var viewZ = pos[2] - fromEye[2] * amountToMoveTowardEye;
        var desiredTextScale = -1 / gl.canvas.height * 2;  // 1x1 pixels
        var scale = viewZ * desiredTextScale;

        var textMatrix = m4.translate(projectionMatrix, viewX, viewY, viewZ);
        // scale the F to the size we need it.
        textMatrix = m4.scale(textMatrix, scale, scale, 1);

        // set texture uniform
        m4.copy(textMatrix, textUniforms.u_matrix);
        webglUtils.setUniforms(textProgramInfo, textUniforms);

        // Draw the text.
        gl.drawArrays(gl.TRIANGLES, 0, vertices.numVertices);
      });

      requestAnimationFrame(drawScene);
    }
  }

  main();
</script>

</html>